/*
 * FileInfo.cpp
 *
 *  Created on: 24.06.2011
 *      Author: mario
 */

#include "FileInfo.h"

using namespace std;

FileInfo::FileInfo()  :
    key(""), filename(""), size(0),
    transferred(0), transfer_accepted(false), transfer_paused(false), try_resume_paused_transfer(false), finished(false),
    receiver(0), pending_data_requests(0), reading(false), cancel_reading(false), bytes_requested(0), bytes_read(0),
    async_open_file(false), read_next(0)

{
}


FileInfo::FileInfo(string key, string filename, uint64_t size, UserPtr partner) :
    key(key), filename(filename), size(size),
    transferred(0), partner(partner), transfer_accepted(false), transfer_paused(false), try_resume_paused_transfer(false), finished(false),
    receiver(0), pending_data_requests(0), reading(false), cancel_reading(false), bytes_requested(0), bytes_read(0),
    async_open_file(false), read_next(0)
{
}

// load FileInfo from PropertyTree
FileInfo::FileInfo(ptree &pt, Userdatabase* users) :
        key(pt.get<string>("key")),
        filename(pt.get<string>("filename")),
        path(pt.get<string>("path")),
        finished(false),
        receiver(0),
        pending_data_requests(0),
        reading(false),
        cancel_reading(false),
        bytes_read(0),
        async_open_file(false),
        read_next(0)
{
    size = pt.get<uint64_t>("size");
    transferred = pt.get<uint64_t>("transferred");
    // TODO Das muss unbedingt schöner gemacht werden, hier ist ja vollkommen unklar ob es ein Infile oder ein Outfile ist,
    // aber es muss unterschiedliches Verhalten sein. ---> Am besten im Zuge wenn Infile- und Outfile-Klassen getrennt werden..
    // ----> Was soll dieser Code überhaupt? War das vielleicht nur Debug? Soll es überhaupt bleiben?
    if (boost::filesystem::is_regular(path))
    {
        uint64_t x = boost::filesystem::file_size(path);
        if ( x == size )
        {
            cout << "File size == SIZE ---> assuming an outfile." << endl;
        }
        else if ( x != transferred)
        {
            cout << "ACHTUNG !! Transferred ist falsch: " << transferred << " -- setze auf: " << x << endl;
            transferred = x;
        }
    }
    else
    {
        transferred = 0;
    }

    partner = users->getUser(pt.get<string>("partner"));
    transfer_accepted = pt.get<bool>("accepted");
    transfer_paused = pt.get<bool>("paused");
    try_resume_paused_transfer = pt.get("try_resume", false);
}

FileInfo::~FileInfo()
{
}


/** getter / setter **/
string FileInfo::get_key()
{
  return key;
}

string FileInfo::get_filename()
{
  return filename;
}

uint64_t FileInfo::get_size()
{
  return size;
}

bool FileInfo::get_acceptance()
{
  return transfer_accepted;
}

TransferBundlePtr FileInfo::get_bundle() const
{
    return bundle;
}



void FileInfo::set_filename(string filename)
{
    this->filename = filename;
}

void FileInfo::set_key(string key)
{
    this->key = key;
}

void FileInfo::set_partner(UserPtr partner)
{
    this->partner = partner;
}

void FileInfo::set_acceptance(bool b)
{
  transfer_accepted = b;

  if ( b == false )
      pending_data_requests = 0;
}

string FileInfo::get_path()
{
  return path;
}

void FileInfo::set_path(string p)
{
  path = p;
}

void FileInfo::set_size(uint64_t size)
{
    this->size = size;
}

UserPtr FileInfo::get_partner()
{
  return partner;
}

uint64_t FileInfo::get_transferred()
{
    return transferred;
}

void FileInfo::set_bundle(TransferBundlePtr bundle)
{
    this->bundle = bundle;
}

// XXX DEPRECATED, man soll gefälligst selber aufpassen, wenn man mit Keys arbeitet.
void FileInfo::change_key(string new_key)
{
    if ( transfer_accepted )
        throw runtime_error("too late to change the key");

    key = new_key;
}

// TODO infile, im outfile muss seek verwendet werden
void FileInfo::skip(uint64_t bytes)
{
    transferred += bytes;
}

// XXX achtung, vgl pause()
void FileInfo::set_pause(bool pause)
{
    transfer_paused = pause;
}

void FileInfo::pause(bool just_hold)
{
    boost::mutex::scoped_lock(buffer_lock);

    // status: paused
    set_pause(true);
    try_resume_paused_transfer = just_hold;

    // clear data buffer and requests
    pending_data_requests = 0;
    data_buffer.clear();
    read_next = ~0; // TODO INVALID-Konstante einführen
    if ( ! reading )
        file_close();
}


// XXX achtung, vgl hold()
void FileInfo::set_hold(bool hold)
{
    try_resume_paused_transfer = hold;
}

void FileInfo::hold()
{
    if (!transfer_paused)
    {
        pause(true);
    }
}


bool FileInfo::is_paused()
{
    return transfer_paused;
}

bool FileInfo::get_try_resume()
{
    return try_resume_paused_transfer;
}

bool FileInfo::is_complete()
{
    return size == transferred;
}

void FileInfo::set_finished()
{
    finished = true;
}

void FileInfo::inc_transferred(uint64_t n)
{
    transferred += n;
}

void FileInfo::seek(uint64_t position)
{
    transferred = position;
    bytes_requested = position;

    {
        boost::mutex::scoped_lock(buffer_lock);
        read_next = position;
    }
}


/** async data access **/
bool FileInfo::request_async_data(DataReceiverInterface* receiver, FilePtr self_ref)
{
    // cancel if there's no more data to get
    if ( bytes_requested >= size || transfer_paused || ! transfer_accepted )
    {
        return false;
    }

    bytes_requested += CHUNKSIZE;

    // set receiver for next chunk of data
    this->receiver = receiver;  // TODO wir setzen das jedesmal, gehen aber davon aus, dass immer das gleiche gesetzt wird...

    // BRANCH: buffer not empty, callback directly
    if ( ! data_buffer.empty() && pending_data_requests == 0 )
    {
        boost::mutex::scoped_lock(buffer_lock);

        PayloadPtr pay = data_buffer.front();
        data_buffer.pop_front();

        // data callback
        receiver->data_receive_callback(partner, pay);
    }
    // BRANCH: buffer empty, schedule a callback
    else
    {
        pending_data_requests++;
    }

    // (re-)start reading
    if ( ! reading && pending_data_requests > 0 )
    {
        self_reference = self_ref;
        reading = true;
        runBlockingMethod();
    }

    return true;
}


// TODO schleife oder wie das jetzt, und wie wird es effizient?
/** Blocking Method Interface -- don't call directly !! **/
void FileInfo::blockingFunction()
{
//    boost::mutex::scoped_lock(file_lock);
    uint64_t pos;

    // open
    if ( ! in_file.is_open() )
    {
        in_file.open(path.c_str(), ios::binary);
    }
    // seek
    {
        boost::mutex::scoped_lock(buffer_lock);
        pos = read_next;
    }
    // TODO INVALID
    if ( pos != ~0 )
        in_file.seekg(pos);
    else
        pos = 0;


    // read from file
    PayloadPtr pay(new Payload_Data(CHUNKSIZE));
    bool read_okay = read_chunk(pay);

    /* buffer operations */
    {
        boost::mutex::scoped_lock(buffer_lock);

        // BRANCH: okay, put chunk into buffer (if read was successful)
        if ( read_okay && read_next == pos )
        {
            read_next += CHUNKSIZE;
            data_buffer.push_back(pay);
//            cout << "BUFFER PUSH: " << pay->get_postion() << ", " << pay->get_size() << endl;
//            dispatch();
        }
        else // TODO wenn read_next invalid, dann schließen. aber wenn ungleich aber gültig, dann lieber neu lesen (schleife)..
        {
            read_next = ~0; // TODO sollte INVALID sein..
            file_close(); //TODO sollte nicht gelockt sein..
        }

        dispatch(); // XXX selbst wenn nichts in den puffer kommt, wird dispatch aufgerufen - sollte aber nicht schlimm sein - wir zZ benötigt, weil sonst "reading" nicht passt
    }
}

void FileInfo::dispatchFunction()
{
    PayloadPtr pay;

    /* synchronized scope */
    {
        boost::mutex::scoped_lock(buffer_lock);

        if ( pending_data_requests > 0 && ! data_buffer.empty())
        {
            pay = data_buffer.front();
            data_buffer.pop_front();
        }
    }

    // serve data request
    if ( pay )
    {
        // data callback
        receiver->data_receive_callback(partner, pay);
        pending_data_requests--;
    }


    // stop reading if buffer is full
//    if ( data_buffer.size() >= FILE_READ_BUFFER_SIZE )
//        reading = false;
//    else
//        runBlockingMethod();
    if ( pending_data_requests > 0 )
    {
        reading = true;
        runBlockingMethod();
    }
    else
    {
        reading = false;
        FilePtr empty;
        self_reference = empty;
    }
}



/** file operations **/
// appends data, returns progress in percent
// FIXME blocking....
int FileInfo::append(char* buff, int len)  // TODO, hier kann man bestimmt die eine oder andere exception schmeißen
{
    // first write -> open file
    file.exceptions(ofstream::failbit);  // XXX experimentell. exception, wenn file nicht geöffnet werden kann..
    if ( ! file.is_open() )
    {
        // create path if it doesn't exist
        boost::filesystem::path path_ = path;
        if ( path_.has_parent_path() && ! boost::filesystem::is_directory(path_.parent_path()) )
        {
            boost::filesystem::create_directories(path_.parent_path());
        }

        // * open file *
        file.open(path.c_str(), ofstream::binary | ofstream::app);
    }
    //  assert(transferred == file.tellp());

    // write
    file.write(buff, len);

    transferred += len;
    return (int) ( (transferred * 100) / size );
}

// reads data from file into given buffer
bool FileInfo::read_chunk(PayloadPtr pay)
{
//    cout << "YYYYYYYYYYYYYYYYYYYYY" << endl << "in_file.tellg() == " << in_file.tellg() << endl << "transferred == " << transferred << endl << endl;

    // read from file
    if ( ! in_file.eof() && ! in_file.fail() )
    {
        // put key into payload-object
        pay->set_key(key);
        bytes_read = in_file.tellg();
        pay->set_position(bytes_read);

        // read
        in_file.read((char*) pay->raw_pointer(), pay->get_size());

        // how much has been read?
        uint64_t n = in_file.gcount();
//        cout << "++++++++++++++++++++ " << n << endl;
        pay->set_size(n);

        // return true if data was actually read
        if ( n > 0 )
            return true;
    }


    // didn't return true yet, no more data to read
    in_file.close();
    return false;
}


void FileInfo::status(ptree &pt)
{
    pt.add("key", key);
    pt.add("filename", filename);
    pt.add("partner", partner->get_name());
    pt.add("transferred", transferred);
    pt.add("size", size);

    string s;
    if ( finished )
        s = "finished";
    else if ( ! partner->is_connected() )
        s = "disconnected";
    else if (transfer_accepted)
    {
        if ( transfer_paused )
            s = "paused";
        else
            s = "active";
    }
    else
    {
        s = "waiting";
    }
    pt.add("state", s);
}


string FileInfo::status_old()
{
    ostringstream s;

    s << "<" << partner->get_name() << "> " << filename;
    if ( filename != key )
        s << " [" << key << "]";
    s << ": " << transferred << "/" << size << " (" << ( (transferred * 100) / size ) << "%)";

    s << " [";
    if ( ! partner->is_connected() )
        s << "disconnected";
    else if (transfer_accepted)
    {
        if ( transfer_paused )
            s << "paused";
        else
            s << "active";
    }
    else
    {
        s << "waiting";
    }
    s << "]";


    return s.str();
}


void FileInfo::save(ptree &pt)
{
    pt.put("key", key);
    pt.put("filename", filename);
    pt.put("size", size);
    pt.put("transferred", transferred);

    pt.put("partner", partner->get_name());
    pt.put("path", path);
    pt.put("accepted", transfer_accepted);

    // always save a transfer as paused, but if it's still running, mark as hold (try_resume)
    pt.put("paused", true);
    if ( ! transfer_paused )
        try_resume_paused_transfer = true;

    pt.put("try_resume", try_resume_paused_transfer);
}


void FileInfo::file_close()
{
  if ( file.is_open() )
  {
    file.close();
  }

  if ( in_file.is_open() )
      in_file.close();
}


/** operators **/
bool FileInfo::operator!(void)
{
  return key.length() == 0;
}
